package com.sika.code.infractructure.generator;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.sika.code.infrastructure.common.base.pojo.domain.valueobject.BaseValueObject;
import com.sika.code.infrastructure.common.base.pojo.query.PageQuery;
import com.sika.code.infrastructure.common.base.test.BaseTestRepository;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * 代码生成器执行器
 *
 * @author daiqi
 * @create 2021-10-29 0:57
 */
@AllArgsConstructor
@Slf4j
public class GeneratorExecutor {
    private final GeneratorDTO generatorDTO;

    private List<TemplateType> getDisableTypes(List<TemplateType> needGenerateTemplateTypes) {
        if (CollUtil.isEmpty(needGenerateTemplateTypes)) {
            return CollUtil.newArrayList(TemplateType.values());
        }
        List<TemplateType> templateTypesForRet = Lists.newArrayList();
        for (TemplateType templateType : TemplateType.values()) {
            if (needGenerateTemplateTypes.contains(templateType)) {
                continue;
            }
            templateTypesForRet.add(templateType);
        }
        return templateTypesForRet;
    }

    /**
     * 数据源配置
     */
    private DataSourceConfig getDataSourceConfig() {
        return new DataSourceConfig.Builder(
                generatorDTO.getDbUrl(),
                generatorDTO.getDbUsername(),
                generatorDTO.getDbPassword())
                .build();
    }

    /**
     * 策略配置
     */
    private StrategyConfig.Builder strategyConfig() {
        return new StrategyConfig.Builder().addInclude(generatorDTO.getTableName()); // 设置需要生成的表名
    }

    /**
     * 全局配置
     */
    private GlobalConfig.Builder globalConfig() {
        GlobalConfig.Builder builder = new GlobalConfig.Builder()
                .author(generatorDTO.getAuthor())
                .disableOpenDir()
                .commentDate("yyyy-MM-dd HH:mm:ss");
        if (generatorDTO.getFileOverride()) {
            builder.fileOverride();
        }
        return builder;
    }

    /**
     * 包配置
     */
    private PackageConfig.Builder packageConfig() {
        return new PackageConfig.Builder();
    }

    /**
     * 模板配置
     */
    private TemplateConfig.Builder templateConfig() {
        return new TemplateConfig.Builder().disable();
    }

    /**
     * 注入配置
     */
    private InjectionConfig.Builder injectionConfig() {
        // 测试自定义输出文件之前注入操作，该操作再执行生成代码前 debug 查看
        return new InjectionConfig.Builder().beforeOutputFile((tableInfo, objectMap) -> {
            System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.toString());
        });
    }


    public void execute() {
        generateInfrastructure();
        generateDomain();
        generateApplication();
        generateInterfaces();
        generateTest();
    }

    private void generateInfrastructure() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = ".pojo";
        generatePo(moduleSubName, templateTypes);
        generateDot(moduleSubName, templateTypes);
        generateQuery(moduleSubName, templateTypes);
        generateValueObject(moduleSubName, templateTypes);
        generateConvert(moduleSubName, templateTypes);
    }

    private void generateDomain() {
        generateMapper();
        generateXml();
        generateRepository();
        generateEntity();
        generateFactory();
        generateAggregateRoot();
    }

    private void generateApplication() {
        generateService();
    }

    private void generateInterfaces() {
        generateController();
//        generateRpc();
    }


    private void generateTest() {
        generateTestRepository();
//        generateTestEntity();
        generateTestService();
        generateTestController();
    }

    private void generateDot(String moduleSubName, List<TemplateType> templateTypes) {
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("DTO")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/dto")
                .setOutDir(generatorDTO.getInfrastructureOutDir())
                .setParent(generatorDTO.getInfrastructureParent());
        generateCore(generateJavaParam);
    }

    private void generatePo(String moduleSubName, List<TemplateType> templateTypes) {
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("PO")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/po")
                .setOutDir(generatorDTO.getInfrastructureOutDir())
                .setParent(generatorDTO.getInfrastructureParent());
        generateCore(generateJavaParam);
    }


    private void generateQuery(String moduleSubName, List<TemplateType> templateTypes) {
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Query")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/query")
                .setOutDir(generatorDTO.getInfrastructureOutDir())
                .setParent(generatorDTO.getInfrastructureParent())
                .setSuperClass(PageQuery.class.getName());
        generateCore(generateJavaParam);
    }

    private void generateConvert(String moduleSubName, List<TemplateType> templateTypes) {
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Converter")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/converter")
                .setOutDir(generatorDTO.getInfrastructureOutDir())
                .setParent(generatorDTO.getInfrastructureParent());
        generateCore(generateJavaParam);
    }


    private void generateValueObject(String moduleSubName, List<TemplateType> templateTypes) {
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("ValueObject")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/valueObject")
                .setOutDir(generatorDTO.getInfrastructureOutDir())
                .setParent(generatorDTO.getInfrastructureParent())
                .setSuperClass(BaseValueObject.class.getName());
        generateCore(generateJavaParam);
    }


    private void generateMapper() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.MAPPER));
        String moduleSubName = ".db";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Mapper")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/mapper")
                .setOutDir(generatorDTO.getDomainOutDir())
                .setParent(generatorDTO.getDomainParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateRepository() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.SERVICE, TemplateType.SERVICEIMPL));
        String moduleSubName = ".db";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Repository")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/repository")
                .setOutDir(generatorDTO.getDomainOutDir())
                .setParent(generatorDTO.getDomainParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateEntity() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = ".domain";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Entity")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/entity")
                .setOutDir(generatorDTO.getDomainOutDir())
                .setParent(generatorDTO.getDomainParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateAggregateRoot() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = StrUtil.EMPTY;
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("AggregateRoot")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/aggregateRoot")
                .setOutDir(generatorDTO.getDomainOutDir())
                .setParent(generatorDTO.getDomainParent())
                .setEntity("aggregate")
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }

    private void generateFactory() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.SERVICE));
        String moduleSubName = ".domain";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Factory")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/factory")
                .setOutDir(generatorDTO.getDomainOutDir())
                .setParent(generatorDTO.getDomainParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }

    private void generateService() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.SERVICE, TemplateType.SERVICEIMPL));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Service")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/service")
                .setOutDir(generatorDTO.getApplicationOutDir())
                .setParent(generatorDTO.getApplicationParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateController() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.CONTROLLER));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Controller")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/controller")
                .setOutDir(generatorDTO.getInterfacesOutDir())
                .setParent(generatorDTO.getInterfacesParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }

    private void generateRpc() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.CONTROLLER));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix(StrUtil.EMPTY)
                .setObjSuffix("Rpc")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/rpc")
                .setOutDir(generatorDTO.getInterfacesOutDir())
                .setParent(generatorDTO.getInterfacesParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateTestRepository() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix("Test")
                .setObjSuffix("Repository")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/repository.test")
                .setOutDir(generatorDTO.getTestOutDir())
                .setParent(generatorDTO.getTestParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateTestController() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix("Test")
                .setObjSuffix("Controller")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/controller.test")
                .setOutDir(generatorDTO.getTestOutDir())
                .setParent(generatorDTO.getTestParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateTestService() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix("Test")
                .setObjSuffix("Service")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/service.test")
                .setOutDir(generatorDTO.getTestOutDir())
                .setParent(generatorDTO.getTestParent())
                .setSuperClass(null);
        generateCore(generateJavaParam);
    }


    private void generateTestEntity() {
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.ENTITY));
        String moduleSubName = "";
        GenerateJavaParam generateJavaParam = new GenerateJavaParam()
                .setObjPrefix("Test")
                .setObjSuffix("Entity")
                .setModuleSubName(moduleSubName)
                .setTemplateTypes(templateTypes)
                .setTemplatePath("/template/entity.test")
                .setOutDir(generatorDTO.getTestOutDir())
                .setParent(generatorDTO.getTestParent())
                .setSuperClass(BaseTestRepository.class.getName());
        generateCore(generateJavaParam);
    }

    private void generateCore(GenerateJavaParam generateJavaParam) {
        String templatePath = generateJavaParam.getTemplatePath();
        String tablePrefix = generatorDTO.getTablePrefix();
        String objSuffix = generateJavaParam.getObjSuffix();
        String objPrefix = generateJavaParam.getObjPrefix();
        String superClass = generateJavaParam.getSuperClass();
        String outDir = generateJavaParam.getOutDir();
        String parent = generateJavaParam.getParent();
        String entity = generateJavaParam.getEntity();
        if (StrUtil.isBlank(entity)) {
            entity = objSuffix.toLowerCase();
        }
        String moduleSubName = generateJavaParam.getModuleSubName();
        List<TemplateType> templateTypes = generateJavaParam.getTemplateTypes();

        String templatePathTemp = templatePath;
        if (!templatePath.contains(".xml")) {
            templatePathTemp = templatePath + ".java";
        }
        AutoGenerator generator = new AutoGenerator(getDataSourceConfig());
        StrategyConfig strategyConfig = strategyConfig().addTablePrefix(tablePrefix)
                .controllerBuilder()
                .formatFileName("%s" + objSuffix)
                .enableRestStyle()
                .serviceBuilder()
                .formatServiceFileName("%s" + objSuffix)
                .formatServiceImplFileName("%s" + objSuffix + "Impl")
                .entityBuilder()
                .enableLombok()
                .formatFileName(objPrefix + "%s" + objSuffix)
                .disableSerialVersionUID()
                .superClass(superClass)
                .addIgnoreColumns("id", "create_date", "update_date",
                        "version", "available", "is_deleted", "remark").build(); // 基于数据库字段
        GlobalConfig globalConfig = globalConfig().outputDir(outDir).build();
        generator.global(globalConfig);

        String entityBodyName = getEntityName(strategyConfig.getInclude().iterator().next());
        String moduleName = entityBodyName.toLowerCase(Locale.ROOT);
        PackageConfig packageConfig = packageConfig().parent(parent + "." + moduleName + moduleSubName)
                .entity(entity)
                .service(objSuffix.toLowerCase())
                .serviceImpl(objSuffix.toLowerCase() + ".impl")
                .controller(objSuffix.toLowerCase())
                .mapper(objSuffix.toLowerCase())
                .build();
        generator.packageInfo(packageConfig);

        generator.strategy(strategyConfig);
        generator.template(templateConfig()
                .entity(templatePathTemp)
                .mapper(templatePathTemp)
                .service(templatePathTemp)
                .serviceImpl(templatePath + "Impl.java")
                .controller(templatePathTemp)
                .mapperXml(templatePathTemp)
                .disable(ArrayUtil.toArray(templateTypes, TemplateType.class))
                .build());

        Map<String, Object> map = new HashMap<>();
        List<String> extendPkg = new ArrayList<>();
        Map<String, String> sikaPackage = Maps.newHashMap();
        sikaPackage.putAll(packageConfig.getPackageInfo());
        sikaPackage.put(ConstVal.MAPPER, getMapperPackage(moduleName));
        sikaPackage.put("PO", getPoPackage(moduleName));
        sikaPackage.put("Query", getQueryPackage(moduleName));
        sikaPackage.put("Entity", getPoPackage(moduleName));
        sikaPackage.put("DTO", getDtoPackage(moduleName));
        sikaPackage.put("Repository", getRepositoryPackage(moduleName));
        sikaPackage.put("Service", getServicePackage(moduleName));
        map.put("sikaPackage", sikaPackage);
        map.put("extendPkg", extendPkg);
        map.put("sikaPrimaryType", "Long");
        map.put("sikaEntityBodyName", entityBodyName);
        Map<String, String> customFile = new HashMap<>();
        generator.injection(injectionConfig().customMap(map).customFile(customFile).build());
        generator.execute(new FreemarkerTemplateEngine());
    }


    private void generateXml() {
        String tablePrefix = generatorDTO.getTablePrefix();
        String infrastructureXmlOutDir = generatorDTO.getMapperXmlOutDir();
        String infrastructureParent = generatorDTO.getInfrastructureParent();
        List<TemplateType> templateTypes = getDisableTypes(Lists.newArrayList(TemplateType.XML));
        AutoGenerator generator = new AutoGenerator(getDataSourceConfig());
        StrategyConfig strategyConfig = strategyConfig().addTablePrefix(tablePrefix)
                .mapperBuilder()
                .formatXmlFileName(generatorDTO.getTableName())
                .enableBaseResultMap()
                .enableBaseColumnList().build(); // 基于数据库字段
        GlobalConfig globalConfig = globalConfig().outputDir(infrastructureXmlOutDir).build();
        generator.global(globalConfig);
        Map<OutputFile, String> pathInfo = Maps.newHashMap();
        pathInfo.put(OutputFile.mapperXml, infrastructureXmlOutDir + "/mapper");
        String entityBodyName = getEntityName(strategyConfig.getInclude().iterator().next());
        String moduleName = entityBodyName.toLowerCase(Locale.ROOT);
        log.info("moduleName:{}", moduleName);
        PackageConfig packageConfig = packageConfig()
                .parent(infrastructureParent + "." + moduleName)
                .entity("pojo.po")
                .mapper("db.mapper")
                .pathInfo(pathInfo)
                .xml("mapper").build();
        generator.packageInfo(packageConfig);

        generator.strategy(strategyConfig);
        generator.template(templateConfig()
                .mapperXml("/template/mapper.xml")
                .disable(ArrayUtil.toArray(templateTypes, TemplateType.class))
                .build());

        Map<String, Object> map = new HashMap<>();
        List<String> extendPkg = new ArrayList<>();
//        extendPkg.add("java.io.Serializable");
        map.put("extendPkg", extendPkg);
        Map<String, String> sikaPackage = Maps.newHashMap();
        sikaPackage.putAll(packageConfig.getPackageInfo());
        sikaPackage.put(ConstVal.MAPPER, getMapperPackage(moduleName));
        sikaPackage.put("Query", getQueryPackage(moduleName));
        map.put("sikaPackage", sikaPackage);
        map.put("sikaEntityBodyName", entityBodyName);

        Map<String, String> customFile = new HashMap<>();
        generator.injection(injectionConfig().customMap(map).customFile(customFile).build());
        generator.execute(new FreemarkerTemplateEngine());
    }

    private String getEntityName(String str) {
        String removePrefix = NamingStrategy.removePrefix(str, Sets.newHashSet(generatorDTO.getTablePrefix()));
        String removeSuffix = NamingStrategy.removePrefix(removePrefix, Sets.newHashSet(generatorDTO.getTableSuffix()));
        return firstToUpperCase(NamingStrategy.underlineToCamel(removeSuffix));
    }

    private String firstToUpperCase(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }


    private String getMapperPackage(String moduleName) {
        return generatorDTO.getDomainParent() + "." + moduleName + ".db.mapper";
    }

    private String getQueryPackage(String moduleName) {
        return generatorDTO.getInfrastructureParent() + "." + moduleName + ".pojo.query";
    }

    private String getPoPackage(String moduleName) {
        return generatorDTO.getInfrastructureParent() + "." + moduleName + ".pojo.po";
    }

    private String getDtoPackage(String moduleName) {
        return generatorDTO.getInfrastructureParent() + "." + moduleName + ".pojo.dto";
    }

    private String getRepositoryPackage(String moduleName) {
        return generatorDTO.getDomainParent() + "." + moduleName + ".db.repository";
    }

    private String getServicePackage(String moduleName) {
        return generatorDTO.getApplicationParent() + "." + moduleName + ".service";
    }

    @Data
    @Accessors(chain = true)
    public static class GenerateJavaParam {
        String objPrefix = StrUtil.EMPTY;
        String objSuffix = StrUtil.EMPTY;
        String moduleSubName = StrUtil.EMPTY;
        List<TemplateType> templateTypes;
        String templatePath = StrUtil.EMPTY;
        String outDir;
        String parent = StrUtil.EMPTY;
        String superClass;
        String entity;
    }
}
